﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CustomTypes.Interfaces;
using CustomTypes.Mono;
using CodeDomHelper.Types;
using Mono.Cecil;
using System.Collections;
using VSLangProj;
using System.IO;
using CustomTypes.Mono.Resolvers;
using devtm.AutoMapper.CustomCode.TypeContainers;
using EnvDTE;
using Microsoft.VisualStudio.Shell.Interop;
using System.Xml;
using System.Diagnostics;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Design;
using Microsoft.VisualStudio.Modeling.Shell;


namespace devtm.AutoMapper
{
    
    [CLSCompliant(false)]
    public class CustomTypeRepository : devtm.AutoMapper.TypeRepository, IServiceProvider
    {

        private Microsoft.VisualStudio.Modeling.Store _store;

        //private AssemblyCSharpDescriptorResolver resolverCSharp;
        internal devtm.Editor.TypeSelector.ParsingSolution.Solution Solution = null;
        private string StoreFilename;
       

        protected CustomTypeRepository(Microsoft.VisualStudio.Modeling.Store store)
        {
            //resolverCSharp = new AssemblyCSharpDescriptorResolver(this);
            this.Store = store;
        }



        public static CustomTypeRepository Instance(Microsoft.VisualStudio.Modeling.Store store, bool renew)
        {
            var i = renew
                ? TypeRepository.NewInstance(() => new CustomTypeRepository(store))
                : TypeRepository.GetInstance(() => new CustomTypeRepository(store));

            if (i.Store != store)            
                i = TypeRepository.NewInstance(() => new CustomTypeRepository(store));
            
            return i;
        }        

        public Microsoft.VisualStudio.Modeling.Store Store 
        { 
            get
            {
                return _store;
            }
            set
            {
                _store = value;

                _containingProject = null;

                if (Solution == null)
                    Solution = new devtm.Editor.TypeSelector.ParsingSolution.Solution(GetSolutionForStore(this.Store));

            }
        }   

        /// <summary>
        /// Get the project Item corresponding to the item in the store
        /// </summary>
        /// <param name="store">Store of interest</param>
        /// <returns>The project item if this method is called from Visual Studio
        /// and <c>null</c> otherwise</returns>
        private static EnvDTE.Solution GetSolutionForStore(Microsoft.VisualStudio.Modeling.Store store)
        {
            if (store == null)
                return null;

            EnvDTE.DTE dte = (EnvDTE.DTE)store.GetService(typeof(EnvDTE.DTE));
            if (dte != null)
                return dte.Solution;
            else
                return null;
        }


        private EnvDTE.Project _containingProject;
        public EnvDTE.Project ContainingProject
        {
            get
            {

                if (_containingProject == null && _store != null)
                {
                    IMonitorSelectionService service = _store.GetService(typeof(IMonitorSelectionService)) as IMonitorSelectionService;
                    if (service != null)
                    {

                        var currentView = service.CurrentDocumentView as ModelingDocView;

                        if (currentView != null)
                            StoreFilename = currentView.DocData.FileName;
                    }

                    _containingProject = Store2DTE.GetProjectForStore(this.Store, StoreFilename);

                }

                return _containingProject;

            }

        }

        public object GetService(Type serviceType)
        {
            return _store.GetService(serviceType);
        }

        public TypeDefinition GetType(string assemblyQualifiedName)
        {
            TypeExpression type = TypeExpression.Analyse(assemblyQualifiedName);
            return base.GetType(type.AssemblyName, type.Namespace, type.Name);
        }

        public TypeDefinition GetType(TypeExpression type)
        {
            return base.GetType(type.AssemblyName, type.Namespace, type.Name);
        }

        public override IAssemblyDescriptor ResolveAssembly(string assemblyName)
        {
            
            IAssemblyDescriptor assembly = null;

            if (_dic.Count == 0)
            {
                try
                {
                    var prj = new devtm.Editor.TypeSelector.ParsingSolution.Project(ContainingProject);

                    // les types de la solution
                    var lst = GetReferences();
                    List = new List<string>();

                    if (lst != null)
                        List.AddRange(lst);

                    // on recupere la liste des assemblies referencés
                    foreach (var item in prj.GetReferencesListeNoInSolution())
                        if (!string.IsNullOrEmpty(item.Path))
                            List.Add(item.Path);

                    foreach (var item in List)
                    {
                        if (File.Exists(item))
                        {
                            CustomTypes.Mono.AssemblyTypeMonoDescriptor t = new CustomTypes.Mono.AssemblyTypeMonoDescriptor(this, item);
                            string NewAssemblyName = t.AssemblyName.Split(',')[0];
                            if (!_dic.ContainsKey(NewAssemblyName))
                                _dic.Add(NewAssemblyName, t);
                        }
                        else
                        {
                            devtm.AutoMapper.Helpers.ShowMessageBox("missing reference", "See the verbatim output panel for more informations");
                            devtm.AutoMapper.Helpers._output.Write("missing reference " + Path.GetFileName(item));
                        }
                    }

                }
                catch (System.TypeLoadException e1)
                {
                    devtm.AutoMapper.Helpers.ShowMessageBox("Type Load Exception", "See the verbatim output panel for more informations");
                    devtm.AutoMapper.Helpers._output.Write(e1.ToString());
                }
                catch (Exception e)
                {
                    devtm.AutoMapper.Helpers._output.Write(e.ToString());
                }

            }

            if (_dic.ContainsKey(assemblyName))
                assembly = _dic[assemblyName];

            return assembly;

        }

        #region References

        #region Dynamic


        public List<string> GetReferences()
        {

            DynamicTypeService typeService = (DynamicTypeService)_store.GetService(typeof(Microsoft.VisualStudio.Shell.Design.DynamicTypeService));
          
            if (typeService == null)
                return null;         

            // les types de la solution
            IVsHierarchy Hierarchy = ToHierarchy(ContainingProject);

            var discovery = typeService.GetTypeResolutionService(Hierarchy);

            if (discovery == null)
                return null;

            // on récupere les projets référencés.
            devtm.Editor.TypeSelector.ParsingSolution.Project ab = new devtm.Editor.TypeSelector.ParsingSolution.Project(ContainingProject);
            var ab2 = ab.GetReferencedProjectListe(false);

            // recupère les assemblies
            List<string> lstAssemblies = GetAssemblies(discovery, ab2.ToList());

            return lstAssemblies;

        }

        public IVsHierarchy ToHierarchy(EnvDTE.Project project)
        {

            if (project == null)
                throw new ArgumentNullException("project");

            string projectGuid = null;

            // DTE does not expose the project GUID that exists at in the msbuild project file.
            // Cannot use MSBuild object model because it uses a static instance of the Engine, 
            // and using the Project will cause it to be unloaded from the engine when the 
            // GC collects the variable that we declare.
            using (XmlReader projectReader = XmlReader.Create(project.FileName))
            {
                projectReader.MoveToContent();
                object nodeName = projectReader.NameTable.Add("ProjectGuid");
                while (projectReader.Read())                
                    if (Object.Equals(projectReader.LocalName, nodeName))
                    {

                        projectGuid = projectReader.ReadElementContentAs(typeof(string), null).ToString();
                        break;
                    }                
            }

            Debug.Assert(!String.IsNullOrEmpty(projectGuid));
            IServiceProvider serviceProvider = new ServiceProvider(project.DTE as Microsoft.VisualStudio.OLE.Interop.IServiceProvider);
            return VsShellUtilities.GetHierarchy(serviceProvider, new Guid(projectGuid));

        }

        public static EnvDTE.Project ToDteProject(IVsHierarchy hierarchy)
        {
            if (hierarchy == null)
                throw new ArgumentNullException("hierarchy");

            object prjObject = null;
            if (hierarchy.GetProperty(0xfffffffe, -2027, out prjObject) >= 0)
                return (EnvDTE.Project)prjObject;
            else
                throw new ArgumentException("Hierarchy is not a project.");

        }

        private static List<string> GetAssemblies(System.ComponentModel.Design.ITypeResolutionService discovery, List<Project> ab2)
        {

            List<string> lstAssemblies = new List<string>();

            foreach (Project item in ab2)
            {
                devtm.Editor.TypeSelector.ParsingSolution.Project ab = new devtm.Editor.TypeSelector.ParsingSolution.Project(item);
                System.Reflection.Assembly aas = discovery.GetAssembly(new System.Reflection.AssemblyName(ab.AssemblyName), true);
                lstAssemblies.Add(aas.Location);
            }

            return lstAssemblies;

        }

        #endregion

        private Hashtable LoadPaths(devtm.Editor.TypeSelector.ParsingSolution.Solution sln)
        {

            Hashtable ListRefs = new Hashtable();


            devtm.Editor.TypeSelector.ParsingSolution.Project prj = new devtm.Editor.TypeSelector.ParsingSolution.Project(ContainingProject);

            foreach (Reference item in prj.GetReferencesListeNoInSolution())
            {
                if (item != null)
                {

                    string n = item.Name;
                    string p = item.Path.ToLower();

                    if (ListRefs.ContainsKey(n))
                    {
                        string p2 = ListRefs[n] as string;

                        if (p2 != p)
                        {

                            DateTime d1 = File.GetLastWriteTime(p2);
                            DateTime d2 = File.GetLastWriteTime(p);

                            if (d2 > d1)
                                ListRefs[n] = p2;

                        }
                    }
                    else
                    {
                        ListRefs.Add(n, p);
                    }

                }
            }

            return ListRefs;

        }

        #endregion

    }
}
